1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import java.applet.Applet;
34 import java.awt.*;
35 import java.awt.event.*;
36 import java.io.*;
37 import java.net.*;
38
39
40 @SuppressWarnings("serial")
41 public class SpreadSheet extends Applet implements MouseListener, KeyListener {
42
43 String title;
44 Font titleFont;
45 Color cellColor;
46 Color inputColor;
47 int cellWidth = 100;
48 int cellHeight = 15;
49 int titleHeight = 15;
50 int rowLabelWidth = 15;
51 Font inputFont;
52 boolean isStopped = false;
53 boolean fullUpdate = true;
54 int rows;
55 int columns;
56 int currentKey = -1;
57 int selectedRow = -1;
58 int selectedColumn = -1;
59 SpreadSheetInput inputArea;
60 Cell cells[][];
61 Cell current = null;
62
63 @Override
64 public synchronized void init() {
65 String rs;
66
67 cellColor = Color.white;
68 inputColor = new Color(100, 100, 225);
69 inputFont = new Font("Monospaced", Font.PLAIN, 10);
70 titleFont = new Font("Monospaced", Font.BOLD, 12);
71 title = getParameter("title");
72 if (title == null) {
73 title = "Spreadsheet";
74 }
75 rs = getParameter("rows");
76 if (rs == null) {
77 rows = 9;
78 } else {
79 rows = Integer.parseInt(rs);
80 }
81 rs = getParameter("columns");
82 if (rs == null) {
83 columns = 5;
84 } else {
85 columns = Integer.parseInt(rs);
86 }
87 cells = new Cell[rows][columns];
88 char l[] = new char[1];
89 for (int i = 0; i < rows; i++) {
90 for (int j = 0; j < columns; j++) {
91
92 cells[i][j] = new Cell(this,
93 Color.lightGray,
94 Color.black,
95 cellColor,
96 cellWidth - 2,
97 cellHeight - 2);
98 l[0] = (char) ((int) 'a' + j);
99 rs = getParameter("" + new String(l) + (i + 1));
100 if (rs != null) {
101 cells[i][j].setUnparsedValue(rs);
102 }
103 }
104 }
105
106 Dimension d = getSize();
107 inputArea = new SpreadSheetInput(null, this, d.width - 2, cellHeight - 1,
108 inputColor, Color.white);
109 resize(columns * cellWidth + rowLabelWidth,
110 (rows + 3) * cellHeight + titleHeight);
111 addMouseListener(this);
112 addKeyListener(this);
113 }
114
115 public void setCurrentValue(float val) {
116 if (selectedRow == -1 || selectedColumn == -1) {
117 return;
118 }
119 cells[selectedRow][selectedColumn].setValue(val);
120 repaint();
121 }
122
123 @Override
124 public void stop() {
125 isStopped = true;
126 }
127
128 @Override
129 public void start() {
130 isStopped = false;
131 }
132
133 @Override
134 public void destroy() {
135 for (int i = 0; i < rows; i++) {
136 for (int j = 0; j < columns; j++) {
137 if (cells[i][j].type == Cell.URL) {
138 cells[i][j].updaterThread.run = false;
139 }
140 }
141 }
142 }
143
144 public void setCurrentValue(int type, String val) {
145 if (selectedRow == -1 || selectedColumn == -1) {
146 return;
147 }
148 cells[selectedRow][selectedColumn].setValue(type, val);
149 repaint();
150 }
151
152 @Override
153 public void update(Graphics g) {
154 if (!fullUpdate) {
155 int cx, cy;
156
157 g.setFont(titleFont);
158 for (int i = 0; i < rows; i++) {
159 for (int j = 0; j < columns; j++) {
160 if (cells[i][j].needRedisplay) {
161 cx = (j * cellWidth) + 2 + rowLabelWidth;
162 cy = ((i + 1) * cellHeight) + 2 + titleHeight;
163 cells[i][j].paint(g, cx, cy);
164 }
165 }
166 }
167 } else {
168 paint(g);
169 fullUpdate = false;
170 }
171 }
172
173 public void recalculate() {
174 int i, j;
175
176
177 for (i = 0; i < rows; i++) {
178 for (j = 0; j < columns; j++) {
179 if (cells[i][j] != null && cells[i][j].type == Cell.FORMULA) {
180 cells[i][j].setRawValue(evaluateFormula(
181 cells[i][j].parseRoot));
182 cells[i][j].needRedisplay = true;
183 }
184 }
185 }
186 repaint();
187 }
188
189 float evaluateFormula(Node n) {
190 float val = 0.0f;
191
192
193
194 if (n == null) {
195
196 return val;
197 }
198 switch (n.type) {
199 case Node.OP:
200 val = evaluateFormula(n.left);
201 switch (n.op) {
202 case '+':
203 val += evaluateFormula(n.right);
204 break;
205 case '*':
206 val *= evaluateFormula(n.right);
207 break;
208 case '-':
209 val -= evaluateFormula(n.right);
210 break;
211 case '/':
212 val /= evaluateFormula(n.right);
213 break;
214 }
215 break;
216 case Node.VALUE:
217
218 return n.value;
219 case Node.CELL:
220 if (cells[n.row][n.column] == null) {
221
222 } else {
223
224 return cells[n.row][n.column].value;
225 }
226 }
227
228
229 return val;
230 }
231
232 @Override
233 public synchronized void paint(Graphics g) {
234 int i, j;
235 int cx, cy;
236 char l[] = new char[1];
237
238
239 Dimension d = getSize();
240
241 g.setFont(titleFont);
242 i = g.getFontMetrics().stringWidth(title);
243 g.drawString((title == null) ? "Spreadsheet" : title,
244 (d.width - i) / 2, 12);
245 g.setColor(inputColor);
246 g.fillRect(0, cellHeight, d.width, cellHeight);
247 g.setFont(titleFont);
248 for (i = 0; i < rows + 1; i++) {
249 cy = (i + 2) * cellHeight;
250 g.setColor(getBackground());
251 g.draw3DRect(0, cy, d.width, 2, true);
252 if (i < rows) {
253 g.setColor(Color.red);
254 g.drawString("" + (i + 1), 2, cy + 12);
255 }
256 }
257
258 g.setColor(Color.red);
259 cy = (rows + 3) * cellHeight + (cellHeight / 2);
260 for (i = 0; i < columns; i++) {
261 cx = i * cellWidth;
262 g.setColor(getBackground());
263 g.draw3DRect(cx + rowLabelWidth,
264 2 * cellHeight, 1, d.height, true);
265 if (i < columns) {
266 g.setColor(Color.red);
267 l[0] = (char) ((int) 'A' + i);
268 g.drawString(new String(l),
269 cx + rowLabelWidth + (cellWidth / 2),
270 cy);
271 }
272 }
273
274 for (i = 0; i < rows; i++) {
275 for (j = 0; j < columns; j++) {
276 cx = (j * cellWidth) + 2 + rowLabelWidth;
277 cy = ((i + 1) * cellHeight) + 2 + titleHeight;
278 if (cells[i][j] != null) {
279 cells[i][j].paint(g, cx, cy);
280 }
281 }
282 }
283
284 g.setColor(getBackground());
285 g.draw3DRect(0, titleHeight,
286 d.width,
287 d.height - titleHeight,
288 false);
289 inputArea.paint(g, 1, titleHeight + 1);
290 }
291
292
293 @Override
294 public void mouseClicked(MouseEvent e) {
295 }
296
297 @Override
298 public void mousePressed(MouseEvent e) {
299 int x = e.getX();
300 int y = e.getY();
301 Cell cell;
302 if (y < (titleHeight + cellHeight)) {
303 selectedRow = -1;
304 if (y <= titleHeight && current != null) {
305 current.deselect();
306 current = null;
307 }
308 e.consume();
309 }
310 if (x < rowLabelWidth) {
311 selectedRow = -1;
312 if (current != null) {
313 current.deselect();
314 current = null;
315 }
316 e.consume();
317
318 }
319 selectedRow = ((y - cellHeight - titleHeight) / cellHeight);
320 selectedColumn = (x - rowLabelWidth) / cellWidth;
321 if (selectedRow > rows
322 || selectedColumn >= columns) {
323 selectedRow = -1;
324 if (current != null) {
325 current.deselect();
326 current = null;
327 }
328 } else {
329 if (selectedRow >= rows) {
330 selectedRow = -1;
331 if (current != null) {
332 current.deselect();
333 current = null;
334 }
335 e.consume();
336 }
337 if (selectedRow != -1) {
338 cell = cells[selectedRow][selectedColumn];
339 inputArea.setText(cell.getPrintString());
340 if (current != null) {
341 current.deselect();
342 }
343 current = cell;
344 current.select();
345 requestFocus();
346 fullUpdate = true;
347 repaint();
348 }
349 e.consume();
350 }
351 }
352
353 @Override
354 public void mouseReleased(MouseEvent e) {
355 }
356
357 @Override
358 public void mouseEntered(MouseEvent e) {
359 }
360
361 @Override
362 public void mouseExited(MouseEvent e) {
363 }
364
365 @Override
366 public void keyPressed(KeyEvent e) {
367 }
368
369 @Override
370 public void keyTyped(KeyEvent e) {
371 fullUpdate = true;
372 inputArea.processKey(e);
373 e.consume();
374 }
375
376 @Override
377 public void keyReleased(KeyEvent e) {
378 }
379
380 @Override
381 public String getAppletInfo() {
382 return "Title: SpreadSheet \nAuthor: Sami Shaio \nA simple spread sheet.";
383 }
384
385 @Override
386 public String[][] getParameterInfo() {
387 String[][] info = {
388 { "title", "string",
389 "The title of the spread sheet. Default is 'Spreadsheet'" },
390 { "rows", "int", "The number of rows. Default is 9." },
391 { "columns", "int", "The number of columns. Default is 5." }
392 };
393 return info;
394 }
395 }
396
397
398 class CellUpdater extends Thread {
399
400 Cell target;
401 InputStream dataStream = null;
402 StreamTokenizer tokenStream;
403 public volatile boolean run = true;
404
405 public CellUpdater(Cell c) {
406 super("cell updater");
407 target = c;
408 }
409
410 @Override
411 public void run() {
412 try {
413 dataStream = new URL(target.app.getDocumentBase(),
414 target.getValueString()).openStream();
415 tokenStream = new StreamTokenizer(new BufferedReader(
416 new InputStreamReader(dataStream)));
417 tokenStream.eolIsSignificant(false);
418
419 while (run) {
420 switch (tokenStream.nextToken()) {
421 case StreamTokenizer.TT_EOF:
422 dataStream.close();
423 return;
424 default:
425 break;
426 case StreamTokenizer.TT_NUMBER:
427 target.setTransientValue((float) tokenStream.nval);
428 if (!target.app.isStopped && !target.paused) {
429 target.app.repaint();
430 }
431 break;
432 }
433 try {
434 Thread.sleep(2000);
435 } catch (InterruptedException e) {
436 break;
437 }
438 }
439 } catch (IOException e) {
440 return;
441 }
442 }
443 }
444
445
446 class Cell {
447
448 public static final int VALUE = 0;
449 public static final int LABEL = 1;
450 public static final int URL = 2;
451 public static final int FORMULA = 3;
452 Node parseRoot;
453 boolean needRedisplay;
454 boolean selected = false;
455 boolean transientValue = false;
456 public int type = Cell.VALUE;
457 String valueString = "";
458 String printString = "v";
459 float value;
460 Color bgColor;
461 Color fgColor;
462 Color highlightColor;
463 int width;
464 int height;
465 SpreadSheet app;
466 CellUpdater updaterThread;
467 boolean paused = false;
468
469 public Cell(SpreadSheet app,
470 Color bgColor,
471 Color fgColor,
472 Color highlightColor,
473 int width,
474 int height) {
475 this.app = app;
476 this.bgColor = bgColor;
477 this.fgColor = fgColor;
478 this.highlightColor = highlightColor;
479 this.width = width;
480 this.height = height;
481 needRedisplay = true;
482 }
483
484 public void setRawValue(float f) {
485 valueString = Float.toString(f);
486 value = f;
487 }
488
489 public void setValue(float f) {
490 setRawValue(f);
491 printString = "v" + valueString;
492 type = Cell.VALUE;
493 paused = false;
494 app.recalculate();
495 needRedisplay = true;
496 }
497
498 public void setTransientValue(float f) {
499 transientValue = true;
500 value = f;
501 needRedisplay = true;
502 app.recalculate();
503 }
504
505 public void setUnparsedValue(String s) {
506 switch (s.charAt(0)) {
507 case 'v':
508 setValue(Cell.VALUE, s.substring(1));
509 break;
510 case 'f':
511 setValue(Cell.FORMULA, s.substring(1));
512 break;
513 case 'l':
514 setValue(Cell.LABEL, s.substring(1));
515 break;
516 case 'u':
517 setValue(Cell.URL, s.substring(1));
518 break;
519 }
520 }
521
522
523
524
525
526
527
528
529
530
531
532
533 public String parseFormula(String formula, Node node) {
534 String subformula;
535 String restFormula;
536 Node left;
537 Node right;
538 char op;
539
540 if (formula == null) {
541 return null;
542 }
543 subformula = parseValue(formula, node);
544
545 if (subformula == null || subformula.length() == 0) {
546
547 return null;
548 }
549 if (subformula.equals(formula)) {
550
551 return formula;
552 }
553
554
555 switch (op = subformula.charAt(0)) {
556 case 0:
557
558 return null;
559 case ')':
560
561 return subformula;
562 case '+':
563 case '*':
564 case '-':
565 case '/':
566 restFormula = subformula.substring(1);
567 subformula = parseValue(restFormula, right = new Node());
568
569 if (subformula == null ? restFormula != null : !subformula.
570 equals(restFormula)) {
571
572 left = new Node(node);
573 node.left = left;
574 node.right = right;
575 node.op = op;
576 node.type = Node.OP;
577
578 return subformula;
579 } else {
580
581 return formula;
582 }
583 default:
584
585 return formula;
586 }
587 }
588
589 public String parseValue(String formula, Node node) {
590 char c = formula.charAt(0);
591 String subformula;
592 String restFormula;
593 float _value;
594 int row;
595 int column;
596
597
598 restFormula = formula;
599 if (c == '(') {
600
601 restFormula = formula.substring(1);
602 subformula = parseFormula(restFormula, node);
603
604 if (subformula == null
605 || subformula.length() == restFormula.length()) {
606
607 return formula;
608 } else if (!(subformula.charAt(0) == ')')) {
609
610 return formula;
611 }
612 restFormula = subformula;
613 } else if (c >= '0' && c <= '9') {
614 int i;
615
616
617 for (i = 0; i < formula.length(); i++) {
618 c = formula.charAt(i);
619 if ((c < '0' || c > '9') && c != '.') {
620 break;
621 }
622 }
623 try {
624 _value = Float.valueOf(formula.substring(0, i)).floatValue();
625 } catch (NumberFormatException e) {
626
627 return formula;
628 }
629 node.type = Node.VALUE;
630 node.value = _value;
631
632 restFormula = formula.substring(i);
633
634
635 return restFormula;
636 } else if (c >= 'A' && c <= 'Z') {
637 int i;
638
639 column = c - 'A';
640 restFormula = formula.substring(1);
641 for (i = 0; i < restFormula.length(); i++) {
642 c = restFormula.charAt(i);
643 if (c < '0' || c > '9') {
644 break;
645 }
646 }
647 row = Float.valueOf(restFormula.substring(0, i)).intValue();
648
649 node.row = row - 1;
650 node.column = column;
651 node.type = Node.CELL;
652
653 if (i == restFormula.length()) {
654 restFormula = null;
655 } else {
656 restFormula = restFormula.substring(i);
657 if (restFormula.charAt(0) == 0) {
658 return null;
659 }
660 }
661 }
662
663 return restFormula;
664 }
665
666 public void setValue(int type, String s) {
667 paused = false;
668 if (this.type == Cell.URL) {
669 updaterThread.run = false;
670 updaterThread = null;
671 }
672
673 valueString = s;
674 this.type = type;
675 needRedisplay = true;
676 switch (type) {
677 case Cell.VALUE:
678 setValue(Float.valueOf(s).floatValue());
679 break;
680 case Cell.LABEL:
681 printString = "l" + valueString;
682 break;
683 case Cell.URL:
684 printString = "u" + valueString;
685 updaterThread = new CellUpdater(this);
686 updaterThread.start();
687 break;
688 case Cell.FORMULA:
689 parseFormula(valueString, parseRoot = new Node());
690 printString = "f" + valueString;
691 break;
692 }
693 app.recalculate();
694 }
695
696 public String getValueString() {
697 return valueString;
698 }
699
700 public String getPrintString() {
701 return printString;
702 }
703
704 public void select() {
705 selected = true;
706 paused = true;
707 }
708
709 public void deselect() {
710 selected = false;
711 paused = false;
712 needRedisplay = true;
713 app.repaint();
714 }
715
716 public void paint(Graphics g, int x, int y) {
717 if (selected) {
718 g.setColor(highlightColor);
719 } else {
720 g.setColor(bgColor);
721 }
722 g.fillRect(x, y, width - 1, height);
723 if (valueString != null) {
724 switch (type) {
725 case Cell.VALUE:
726 case Cell.LABEL:
727 g.setColor(fgColor);
728 break;
729 case Cell.FORMULA:
730 g.setColor(Color.red);
731 break;
732 case Cell.URL:
733 g.setColor(Color.blue);
734 break;
735 }
736 if (transientValue) {
737 g.drawString("" + value, x, y + (height / 2) + 5);
738 } else {
739 if (valueString.length() > 14) {
740 g.drawString(valueString.substring(0, 14),
741 x, y + (height / 2) + 5);
742 } else {
743 g.drawString(valueString, x, y + (height / 2) + 5);
744 }
745 }
746 }
747 needRedisplay = false;
748 }
749 }
750
751
752 class Node {
753
754 public static final int OP = 0;
755 public static final int VALUE = 1;
756 public static final int CELL = 2;
757 int type;
758 Node left;
759 Node right;
760 int row;
761 int column;
762 float value;
763 char op;
764
765 public Node() {
766 left = null;
767 right = null;
768 value = 0;
769 row = -1;
770 column = -1;
771 op = 0;
772 type = Node.VALUE;
773 }
774
775 public Node(Node n) {
776 left = n.left;
777 right = n.right;
778 value = n.value;
779 row = n.row;
780 column = n.column;
781 op = n.op;
782 type = n.type;
783 }
784
785 public void indent(int ind) {
786 for (int i = 0; i < ind; i++) {
787 System.out.print(" ");
788 }
789 }
790
791 public void print(int indentLevel) {
792 char l[] = new char[1];
793 indent(indentLevel);
794 System.out.println("NODE type=" + type);
795 indent(indentLevel);
796 switch (type) {
797 case Node.VALUE:
798 System.out.println(" value=" + value);
799 break;
800 case Node.CELL:
801 l[0] = (char) ((int) 'A' + column);
802 System.out.println(" cell=" + new String(l) + (row + 1));
803 break;
804 case Node.OP:
805 System.out.println(" op=" + op);
806 left.print(indentLevel + 3);
807 right.print(indentLevel + 3);
808 break;
809 }
810 }
811 }
812
813
814 class InputField {
815
816 int maxchars = 50;
817 int cursorPos = 0;
818 Applet app;
819 String sval;
820 char buffer[];
821 int nChars;
822 int width;
823 int height;
824 Color bgColor;
825 Color fgColor;
826
827 public InputField(String initValue, Applet app, int width, int height,
828 Color bgColor, Color fgColor) {
829 this.width = width;
830 this.height = height;
831 this.bgColor = bgColor;
832 this.fgColor = fgColor;
833 this.app = app;
834 buffer = new char[maxchars];
835 nChars = 0;
836 if (initValue != null) {
837 initValue.getChars(0, initValue.length(), this.buffer, 0);
838 nChars = initValue.length();
839 }
840 sval = initValue;
841 }
842
843 public void setText(String val) {
844 int i;
845
846 for (i = 0; i < maxchars; i++) {
847 buffer[i] = 0;
848 }
849 if (val == null) {
850 sval = "";
851 } else {
852 sval = val;
853 }
854 nChars = sval.length();
855 sval.getChars(0, sval.length(), buffer, 0);
856 }
857
858 public String getValue() {
859 return sval;
860 }
861
862 public void paint(Graphics g, int x, int y) {
863 g.setColor(bgColor);
864 g.fillRect(x, y, width, height);
865 if (sval != null) {
866 g.setColor(fgColor);
867 g.drawString(sval, x, y + (height / 2) + 3);
868 }
869 }
870
871 public void processKey(KeyEvent e) {
872 char ch = e.getKeyChar();
873 switch (ch) {
874 case '\b':
875 if (nChars > 0) {
876 nChars--;
877 sval = new String(buffer, 0, nChars);
878 }
879 break;
880 case '\n':
881 selected();
882 break;
883 default:
884 if (nChars < maxchars && ch >= '0') {
885 buffer[nChars++] = ch;
886 sval = new String(buffer, 0, nChars);
887 }
888 }
889 app.repaint();
890 }
891
892 public void keyReleased(KeyEvent e) {
893 }
894
895 public void selected() {
896 }
897 }
898
899
900 class SpreadSheetInput
901 extends InputField {
902
903 public SpreadSheetInput(String initValue,
904 SpreadSheet app,
905 int width,
906 int height,
907 Color bgColor,
908 Color fgColor) {
909 super(initValue, app, width, height, bgColor, fgColor);
910 }
911
912 @Override
913 public void selected() {
914 float f;
915 sval = ("".equals(sval)) ? "v" : sval;
916 switch (sval.charAt(0)) {
917 case 'v':
918 String s = sval.substring(1);
919 try {
920 int i;
921 for (i = 0; i < s.length(); i++) {
922 char c = s.charAt(i);
923 if (c < '0' || c > '9') {
924 break;
925 }
926 }
927 s = s.substring(0, i);
928 f = Float.valueOf(s).floatValue();
929 ((SpreadSheet) app).setCurrentValue(f);
930 } catch (NumberFormatException e) {
931 System.out.println("Not a float: '" + s + "'");
932 }
933 break;
934 case 'l':
935 ((SpreadSheet) app).setCurrentValue(Cell.LABEL,
936 sval.substring(1));
937 break;
938 case 'u':
939 ((SpreadSheet) app).setCurrentValue(Cell.URL, sval.substring(1));
940 break;
941 case 'f':
942 ((SpreadSheet) app).setCurrentValue(Cell.FORMULA,
943 sval.substring(1));
944 break;
945 }
946 }
947 }